無論是使用者輸入老師或是課名,同樣都是使用 PostbackEvent
觸發事件搭配 FlexSendMessage
訊息回覆,就能跳出課程列表或是開課老師列表,其邏輯也大致相同。
如果要完全再寫一個一樣的函式處理課名,也是完全可行的,但若我們使用了同一份 teacher_lists_flex_message_package
呢?
無論課程列表或是開課老師列表都重複利用同一個函式,這樣程式碼就會變得更簡潔,但如果太簡潔又會看不懂,所以兼具易讀與重複利用高的平衡,是所有軟體工程師的生涯課題,畢竟通常專案都會有一群人一起維護,不像是自己的房間要多亂都沒關西。
更名檔案使其與涵義一致:因為都是使用同一套模板,先將 reply_course_list.json
重新取名,改為 reply_course_teacher_list.json
,養成好習慣,以便日後辨識。
條件句判斷查詢老師/課名:在監聽使用者傳來的文字訊息時,也要判斷傳來的是課名還是老師名,順手也將函式改為 handle_msg
:
# hulolo > chatbot > views.py
@parser.add(MessageEvent, message=TextMessage)
def handle_msg(event):
user_message = event.message.text #取得使用者發送的文字
# 以不同條件做搜尋
filtered_teacher = Course.objects.filter(teacher_name=user_message)
filtered_course = Course.objects.filter(course_name=user_message)
透過二分法設計條件句,先添上老師名的判斷:
# hulolo > chatbot > views.py
# 接續上段程式碼
# 判斷老師名的物件是否存在資料
if filtered_teacher.exists():
teacher_name = user_message
candidate_courses = filtered_teacher.values_list(
'course_name', flat=True).distinct()
# 修改函式名稱為 dynamic_flex_message_package
# 多了 label_type 參數讓函式能辨識來源為何
flex_message = dynamic_flex_message_package(
message = FlexSendMessage(
alt_text=f"{teacher_name} 老師的課程",
contents=flex_message
)
line_bot_api.reply_message(
event.reply_token,
message
)
elif
的部分再添上課程名的判斷:
# hulolo > chatbot > views.py
# 接續上段程式碼
# 判斷課程名的物件是否存在資料
elif filtered_course.exists():
course_name = user_message
candidate_teachers = filtered_course.values_list(
'teacher_name', flat=True).distinct()
# 同樣地,稍後會製作這個漂亮的函式,先呼叫它
# 多了 label_type 變數讓函式能辨識
flex_message = dynamic_flex_message_package(
course_name, candidate_teachers, label_type='teacher')
message = FlexSendMessage(
alt_text=f"{course_name} 的老師",
contents=flex_message
)
line_bot_api.reply_message(
event.reply_token,
message)
Flex message 的模板處理: dynamic_flex_message_package
引入了三個參數,也做了更名,改為較為中性的 title_name
以及 candidate_list
,也別忘了 label_type
:
# hulolo > chatbot > views.py
def dynamic_flex_message_package(title_name, candidate_list, label_type):
# 引入 JSON 檔案
json_path = os.path.join(BASE_DIR, 'chatbot', 'reply_course_teacher_list.json')
flex = json.load(open(json_path, 'r', encoding='utf-8'))
# 設定標題為傳入的名稱 (可以是老師名或課程名) 這邊使用了單行條件句
flex['body']['contents'][0]['text'] = f"哪位老師的{title_name}?" if
label_type == 'teacher' else f"{title_name}老師的哪堂課?"
# 設定顏色列表
colors = ['#F0C29E', '#A1DE95', '#F5C578', '#91D9C2', '#DFC493',
'#F0C29E', '#A1DE95', '#F5C578', '#91D9C2', '#DFC493']
根據傳入的 candidate_list
動態生成按鈕:
# hulolo > chatbot > views.py
# 接續上方程式碼
# 根據傳入的 candidate_list 動態生成按鈕
for i, name in enumerate(candidate_list, start=1):
button = {
'type': 'box',
'layout': 'vertical',
'spacing': 'none',
'contents': [{
'type': 'button',
'style': 'primary',
'action': {
'type': 'postback',
'label': name,
# 注意這裡的 data 格式,使用了二分法,根據傳入的參數動態生成資料
# 因為兩者課名與老師名的順序對調會讓 handle_postback 失效
'data': f"{name}-{title_name}" if
label_type == "teacher" else f"{title_name}-{name}"
},
'color': colors[i-1],
'margin': 'xs',
'offsetTop': 'none',
'height': 'sm'
}],
'flex': 0,
'borderWidth': 'medium',
'cornerRadius': 'xxl',
'offsetTop': 'none',
'margin': 'md',
'backgroundColor': colors[i-1]
}
flex['body']['contents'].append(button)
return flex
編寫的時候別把課名、老師名相互搞混了~
無論輸入 吳小峰 或 特殊教育學生評量 都能夠跑出對應的列表囉:
handle_msg
函式中的條件句其實也有高度類似,可以試著重構讓其更簡潔,但別顧著簡潔失了可讀性。在這篇文章中,我們學會了:
label_type
參數的代入能夠判斷主程式的類型 (以老師名查詢/以課名查詢)title_name
及 candidate_list
中性的命名可以容易了解變數的用途
teacher_name
及 candidate_courses